package org.dromara.common.redis.config

import com.fasterxml.jackson.annotation.JsonAutoDetect
import com.fasterxml.jackson.annotation.PropertyAccessor
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer
import io.github.oshai.kotlinlogging.KotlinLogging
import org.dromara.common.core.utils.SpringUtils.isVirtual
import org.dromara.common.redis.config.properties.RedissonProperties
import org.dromara.common.redis.handler.KeyPrefixHandler
import org.dromara.common.redis.handler.RedisExceptionHandler
import org.redisson.client.codec.StringCodec
import org.redisson.codec.CompositeCodec
import org.redisson.codec.TypedJsonJacksonCodec
import org.redisson.config.Config
import org.redisson.spring.starter.RedissonAutoConfigurationCustomizer
import org.springframework.boot.autoconfigure.AutoConfiguration
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.core.task.VirtualThreadTaskExecutor
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
import java.util.*

/**
 * redis配置
 *
 * @author Lion Li
 * @updater LikeYouDo
 * @date 2025/2/2 3:27
 */
@AutoConfiguration
@EnableConfigurationProperties(RedissonProperties::class)
class RedisConfig(
    private val redissonProperties: RedissonProperties
) {
    companion object {
        private val log = KotlinLogging.logger { }
    }

    @Bean
    fun redissonCustomizer(): RedissonAutoConfigurationCustomizer {
        return RedissonAutoConfigurationCustomizer { config: Config ->
            val javaTimeModule = JavaTimeModule()
            val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
            javaTimeModule.addSerializer(LocalDateTime::class.java, LocalDateTimeSerializer(formatter))
            javaTimeModule.addDeserializer(LocalDateTime::class.java, LocalDateTimeDeserializer(formatter))
            val om = ObjectMapper()
            om.registerModule(javaTimeModule)
            om.setTimeZone(TimeZone.getDefault())
            om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY)
            // 指定序列化输入的类型，类必须是非final修饰的。序列化时将对象全类名一起保存下来
            // kotlin的数据类没有使用allopen插件是最终类 反序列化会失败
            // 处理方式: 1.添加@AllOpen注解让数据类不是final修饰  2.修改下面 NON_FINAL 为 EVERYTHING ,让每个类序列化时将对象全类名一起保存下来
            om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.EVERYTHING)
            // LoggerFactory.useSlf4jLogging(true);
            // val furyCodec = FuryCodec();
            // val codec = CompositeCodec(StringCodec.INSTANCE, furyCodec, furyCodec);
            val jsonCodec = TypedJsonJacksonCodec(Any::class.java, om)
            // 组合序列化 key 使用 String 内容使用通用 json 格式
            val codec = CompositeCodec(StringCodec.INSTANCE, jsonCodec, jsonCodec)
            config.setThreads(redissonProperties.threads)
                .setNettyThreads(redissonProperties.nettyThreads) // 缓存 Lua 脚本 减少网络传输(redisson 大部分的功能都是基于 Lua 脚本实现)
                .setUseScriptCache(true).codec = codec
            if (isVirtual()) {
                config.nettyExecutor = VirtualThreadTaskExecutor("redisson-")
            }
            val singleServerConfig = redissonProperties.singleServerConfig
            if (singleServerConfig != null) {
                // 使用单机模式
                config.useSingleServer()
                    // 设置redis key前缀
                    .setNameMapper(KeyPrefixHandler(redissonProperties.keyPrefix))
                    .setTimeout(singleServerConfig.timeout)
                    .setClientName(singleServerConfig.clientName)
                    .setIdleConnectionTimeout(singleServerConfig.idleConnectionTimeout)
                    .setSubscriptionConnectionPoolSize(singleServerConfig.subscriptionConnectionPoolSize)
                    .setConnectionMinimumIdleSize(singleServerConfig.connectionMinimumIdleSize).connectionPoolSize =
                    singleServerConfig.connectionPoolSize
            }
            // 集群配置方式 参考下方注释
            val clusterServersConfig = redissonProperties.clusterServersConfig
            if (clusterServersConfig != null) {
                config.useClusterServers()
                    // 设置redis key前缀
                    .setNameMapper(KeyPrefixHandler(redissonProperties.keyPrefix))
                    .setTimeout(clusterServersConfig.timeout)
                    .setClientName(clusterServersConfig.clientName)
                    .setIdleConnectionTimeout(clusterServersConfig.idleConnectionTimeout)
                    .setSubscriptionConnectionPoolSize(clusterServersConfig.subscriptionConnectionPoolSize)
                    .setMasterConnectionMinimumIdleSize(clusterServersConfig.masterConnectionMinimumIdleSize)
                    .setMasterConnectionPoolSize(clusterServersConfig.masterConnectionPoolSize)
                    .setSlaveConnectionMinimumIdleSize(clusterServersConfig.slaveConnectionMinimumIdleSize)
                    .setSlaveConnectionPoolSize(clusterServersConfig.slaveConnectionPoolSize)
                    .setReadMode(clusterServersConfig.readMode).subscriptionMode = clusterServersConfig.subscriptionMode
            }
            log.info { "初始化 redis 配置" }
        }
    }

    /**
     * 异常处理器
     */
    @Bean
    fun redisExceptionHandler(): RedisExceptionHandler {
        return RedisExceptionHandler()
    }

    /**
     * redis集群配置 yml
     *
     * --- # redis 集群配置(单机与集群只能开启一个另一个需要注释掉)
     * spring.data:
     *   redis:
     *     cluster:
     *       nodes:
     *         - 192.168.0.100:6379
     *         - 192.168.0.101:6379
     *         - 192.168.0.102:6379
     *     # 密码
     *     password:
     *     # 连接超时时间
     *     timeout: 10s
     *     # 是否开启ssl
     *     ssl.enabled: false
     *
     * redisson:
     *   # 线程池数量
     *   threads: 16
     *   # Netty线程池数量
     *   nettyThreads: 32
     *   # 集群配置
     *   clusterServersConfig:
     *     # 客户端名称
     *     clientName: ${ruoyi.name}
     *     # master最小空闲连接数
     *     masterConnectionMinimumIdleSize: 32
     *     # master连接池大小
     *     masterConnectionPoolSize: 64
     *     # slave最小空闲连接数
     *     slaveConnectionMinimumIdleSize: 32
     *     # slave连接池大小
     *     slaveConnectionPoolSize: 64
     *     # 连接空闲超时，单位：毫秒
     *     idleConnectionTimeout: 10000
     *     # 命令等待超时，单位：毫秒
     *     timeout: 3000
     *     # 发布和订阅连接池大小
     *     subscriptionConnectionPoolSize: 50
     *     # 读取模式
     *     readMode: "SLAVE"
     *     # 订阅模式
     *     subscriptionMode: "MASTER"
     */

}
